#include "MasterService.h"
#include "Server/LogServiceFactory.h"
#include "base/core/lua_parser.h"
#include "base/core/os/debug.h"
#include "base/core/Profiler.h"
#include "base/core/random.h"
#include "Common/Utility/ServiceUtility.h"
#include "Common/Utility/ServerTime.h"
#include "Common/Utility/TimeUtility.h"
#include "Common/NetPack/NetPack.h"
#include "ProtoBuffer/AllProtocol.h"
#include "MasterMgr.h"

namespace
{
	static const char s_config_file[] = "./config.lua";
}

Service*  MakeMasterService()
{
	return MasterService::Instance();
}

void  DeleteMasterService(Service* service)
{
	_UNUSE(service);
	MasterService::Destroy();
}

MasterService::MasterService()
	: Service(MASTER_SERVER)
	, m_listenPort(0)
	, m_listenHandle(nullptr)
	, m_syncStateTick(0)
	, m_pMessageDispatch(nullptr)
	, m_pWebSession(nullptr)

{
	memset(m_listenIp, 0, sizeof(m_listenIp));
}

MasterService::~MasterService()
{}

bool MasterService::Init(Net* net, const char* configFile)
{
	bool result = false;
	bool retCode = false;

	MMO_ASSERT(net);
	m_net = net;

	m_pMessageDispatch = new MessageDispatch(this);
	MMOLOG_PROCESS_ERROR(m_pMessageDispatch);

	retCode = (g_MakeMasterMgr() != nullptr);
	MMOLOG_PROCESS_ERROR(retCode);

	retCode = _LoadConfig(configFile);
	MMOLOG_PROCESS_ERROR(retCode);

	retCode = _ListenAddress();
	MMOLOG_PROCESS_ERROR(retCode);

	snprintf(m_srvTotalName, SERVER_NAME, "master%d", m_id);
	SetRuningStatus(STATUS_RUNNING);

	result = true;
Exit0:
	return result;
}

bool  MasterService::_LoadConfig(const char* config_file) 
{
	bool result = false;
	bool retCode = false;
	LuaParser  parser;
	bool bAllZoneOpen = false;

	MMO_ASSERT(config_file);
	if (!parser.OpenFile(config_file)) 
	{
		LogErrFmtPrint("[log] open config file %s fail", config_file);
		goto Exit0;
	}

	{
		LuaRef router = parser.GetRef("Master");
		MMOLOG_PROCESS_ERROR(router.isTable());

		retCode = GetLuaTableValue(router, "ID", &m_id);
		MMOLOG_PROCESS_ERROR(retCode);
		retCode = GetLuaTableValue(router, "IP", m_listenIp, countof(m_listenIp));
		MMOLOG_PROCESS_ERROR(retCode);
		retCode = GetLuaTableValue(router, "Port", &m_listenPort);
		MMOLOG_PROCESS_ERROR(retCode);

		retCode = GetLuaTableValue(router, "AllZoneOpen", &bAllZoneOpen);
		MMOLOG_PROCESS_ERROR(retCode);

		LuaRef  connect = router["ConnectServers"];
		MMOLOG_PROCESS_ERROR(connect.isTable());

		retCode = LoadConfigConnect2Service(connect, "Log", &m_logServer);
		MMOLOG_PROCESS_ERROR(retCode);
		m_logServer.connectState = CONNECT_STATE_READY;
	}

	retCode = g_GetMasterMgr()->Init(bAllZoneOpen);
	MMOLOG_PROCESS_ERROR(retCode);

	result = true;
Exit0:
	return result;
}

bool  MasterService::_ListenAddress() 
{
	bool result = false;
	ConnectPeer peer;

	MMOLOG_ASSERT_EXIT(m_net);
	MMOLOG_ASSERT_EXIT(m_listenPort != 0);

	peer.ip = m_listenIp;
	peer.port = m_listenPort;
	peer.req = (void*)MasterService::_OnAcceptServer;
	peer.context = this;
	peer.sub_func = SubServerNetPack;
	peer.recvBufSize = INNER_RECVBUF_DEFAULT_SIZE;
	peer.sendBufSize = INNER_SENDBUF_DEFAULT_SIZE;

	m_listenHandle = m_net->Listen(&peer);
	if (m_listenHandle == NULL) 
	{
		LogErrFmtPrint("[master] listen(%s:%d) fail", m_listenIp, m_listenPort);
		goto Exit0;
	}
	LogInfoFmtPrint("[master] listen(%s:%d) success", m_listenIp, m_listenPort);
	result = true;
Exit0:
	return result;
}

bool MasterService::_OnAcceptServer(ISession* session, void* context) 
{
	PROFILE_SEGMENT("GameRouterService::_OnAcceptServer");

	MMO_ASSERT(session);
	MMO_ASSERT(context);
	MasterService* service = (MasterService*)context;

	session->SetUserReq((void*)(MasterService::_OnCloseServer), context);
	session->SetNetPackHandle((void*)(service->GetServiceMessageDispatch()));

	service->ResetSessionTimeOut(session, MAX_SERVER_TIME_MSEC);
	LogInfoFmtPrint("[master] server %s connected", session->GetRemoteAddress().IPAndPort);

	return true;
}

bool  MasterService::_OnCloseServer(ISession* session, void* context, CloseFlag flag) 
{
    _UNUSE(flag);

    bool result = false;
	//bool retcode = false;

    SESSION_USER_DATA userData;
    MasterService* service = NULL;
	uint16_t serviceType = SERVER_TYPE_NULL;
	std::string       sessionName = "unknown";
	uint32_t          serverID = 0;

    MMOLOG_ASSERT_EXIT(session != NULL);
    MMOLOG_ASSERT_EXIT(context != NULL);
	userData = session->GetUserData();
	serviceType = session->GetMark();
	serverID = userData.v32;

    service = (MasterService*)context;
 	MMOLOG_PROCESS_ERROR(service != nullptr);

	if (serviceType > SERVER_TYPE_NULL && serviceType < SERVICE_COUNT)
	{
		sessionName = s_service_name[serviceType];
	}
	else if (serviceType > WEB_SERVER_TYPE_NULL && serviceType < WEB_SERVICE_COUNT)
	{
		sessionName = s_web_service_name[serviceType % 100];
	}

	switch (serviceType)
	{
	case LOG_SERVER:
		service->m_logServer.session = nullptr;
		service->m_logServer.connectState = CONNECT_STATE_READY;
		break;
	case ZONE_SERVER:
		service->m_zoneSessionMap.erase(serverID);
		g_GetMasterMgr()->ClearZoneInfo(serverID);
		break;
	case WEB_MASTER_SERVER:
		service->m_pWebSession = nullptr;
		break;
	case LOGIN_SERVER:
		service->m_loginSessionMap.erase(serverID);
		break;
	default:
		break;
	}

    LogInfoFmtPrint("[master] server(nane:%s,id:%u) lost connect from %s",
					sessionName.c_str(),
					userData.v32,
					session->GetRemoteAddress().IPAndPort);
    result = true;
Exit0:
    return result;
}

void  MasterService::UnInit()
{
	if (m_listenHandle != nullptr) 
	{
		m_net->CloseListen(m_listenHandle);
		m_listenHandle = nullptr;
	}

	for (auto iter = m_zoneSessionMap.begin(); iter != m_zoneSessionMap.end(); iter++)
	{
		if (iter->second != NULL)
		{
			m_net->Close(iter->second);
		}
	}

	for (auto iter = m_loginSessionMap.begin(); iter != m_loginSessionMap.end(); iter++)
	{
		if (iter->second != NULL)
		{
			m_net->Close(iter->second);
		}
	}
	m_zoneSessionMap.clear();
	m_loginSessionMap.clear();

	g_DelMasterMgr();

	MMO_DELETE(m_pMessageDispatch);
}

bool MasterService::Update(uint64_t tick)
{
	bool result = false;

	BEGIN_PROFILE("MasterService::_ProcessConnection");
	_ProcessConnection();
	END_PROFILE();

	BEGIN_PROFILE("MasterService::_SyncServiceState");
	_SyncServiceState();
	END_PROFILE();

	BEGIN_PROFILE("LoginService::SendErrLog");
	if (ServerTime::CheckPerSecs(120)) 
	{
		SendErr(m_logServer.session, EMODULE_ID_SERV_SYS, SERVER_ERR_LOG_COLLECT);
	}
	END_PROFILE();

	if (IsQuitting() && _CheckQuittingComplete()) 
	{
		goto Exit0;
	}
	result = true;
Exit0:
	return result;
}

bool MasterService::Quit()
{
	bool result = false;

	MMOLOG_PROCESS_ERROR(IsRuning());
	SetRuningStatus(STATUS_QUITTING);
	result = true;
Exit0:
	return result;
}

bool MasterService::_CheckQuittingComplete() 
{
	bool result = false;
	MMO_PROCESS_ERROR(IsQuitting());
	result = true;
Exit0:
	return result;
}

bool MasterService::HandleConsoleCmd(const char* cmd)
{
	return (cmd[0] != '\0');
}

IMessageDispatch *MasterService::GetServiceMessageDispatch()
{
	return m_pMessageDispatch;
}

bool MasterService::_ProcessConnection() 
{
	bool result = false;
	bool retCode = false;
	time_t timeInSecond = Time::Now().UnixSec();

	// log
	retCode = ConnectService(m_net, &m_logServer, timeInSecond, MasterService::_OnConnectLogServer, this);
	MMOLOG_PROCESS_ERROR(retCode);

	result = true;
Exit0:
	return result;
}

bool MasterService::_SyncServiceState() 
{
	uint64_t tick = Time::Tick();

	if (tick < m_syncStateTick + SERVER_PING_INTERVAL * 1000)
		return true;

	if (m_logServer.connectState == CONNECT_STATE_CONNECTED)
	{
		SendData(m_logServer.session, EMODULE_ID_SERV_SYS, LOG_SERVER_PROTOCOL_PING);
	}
	m_syncStateTick = tick;
	return true;
}

bool MasterService::_OnConnectLogServer(ISession* session, void* context, bool success)
{
	PROFILE_SEGMENT(" GameRouterService::_OnConnectAccountDBServer");

	bool result = false;
	SESSION_USER_DATA userdata;
	MasterService* service = NULL;
	ServiceConnect* serviceConnect = NULL;

	MMOLOG_ASSERT_EXIT(context != NULL);
	service = (MasterService*)context;
	serviceConnect = &service->m_logServer;

	if (success) 
	{
		MMOLOG_ASSERT_EXIT(session != NULL);
		serviceConnect->session = session;
		serviceConnect->connectState = CONNECT_STATE_CONNECTED;
		serviceConnect->serviceType = LOG_SERVER;
		serviceConnect->session->SetUserReq((void*)(MasterService::_OnCloseServer), context);
		serviceConnect->session->SetNetPackHandle((void*)(service->GetServiceMessageDispatch()));
		LogInfoFmtPrint("[master] connect log server(id:%u, ip:%s:%d) success",
						serviceConnect->id,
						serviceConnect->remoteIp,
						serviceConnect->remotePort);

		userdata.v32 = serviceConnect->id;
		session->SetUserData(userdata);
		session->SetMark(LOG_SERVER);
	}
	else 
	{
		LogErrFmtPrint("[master] connect log server(id:%u, ip:%s:%d) fail",
						serviceConnect->id,
						serviceConnect->remoteIp,
						serviceConnect->remotePort);
		serviceConnect->connectState = CONNECT_STATE_READY;
	}

	result = true;
Exit0:
	return result;
}

bool MasterService::SendDataToLogin(uint32_t loginId, uint16_t module, uint16_t cmd, ProtoBufBase* buf /* = nullptr */)
{
	bool result = false;
	bool retCode = false;

	SESSION_MAP::iterator iter = m_loginSessionMap.find(loginId);
	SESSION_MAP::iterator iterEnd = m_loginSessionMap.end();
	MMOLOG_PROCESS_ERROR(iter != iterEnd);

	if (buf == nullptr)
	{
		retCode = SendData(iter->second, module, cmd);
	}
	else
	{
		retCode = SendData(iter->second, module, cmd, buf);
	}
	MMOLOG_PROCESS_ERROR(retCode);

	result = true;
Exit0:
	return result;
}

bool MasterService::SendDataToAllLogin(uint16_t module, uint16_t cmd, ProtoBufBase* buf /* = nullptr */)
{
	bool retCode = false;

	SESSION_MAP::iterator iter = m_loginSessionMap.begin();
	SESSION_MAP::iterator iterEnd = m_loginSessionMap.end();

	for (; iter != iterEnd; iter++)
	{
		if (buf == nullptr)
		{
			retCode = SendData(iter->second, module, cmd);
		}
		else
		{
			retCode = SendData(iter->second, module, cmd, buf);
		}
		if (!retCode)
			LogErrFmtPrint("SendDataToAllLogin, err loginId:%d ", iter->first);
	}
	return true;
}

bool MasterService::SendDataToZone(uint32_t zid, uint16_t module, uint16_t cmd, ProtoBufBase* buf /* = nullptr */)
{
	bool result = false;
	bool retCode = false;

	SESSION_MAP::iterator iter = m_zoneSessionMap.find(zid);
	SESSION_MAP::iterator iterEnd = m_zoneSessionMap.end();
	MMOLOG_PROCESS_ERROR(iter != iterEnd);

	if (buf == nullptr)
	{
		retCode = SendData(iter->second, module, cmd);
	}
	else
	{
		retCode = SendData(iter->second, module, cmd, buf);
	}
	MMOLOG_PROCESS_ERROR(retCode);

	result = true;
Exit0:
	return result;
}

bool MasterService::SendDataToWeb(uint16_t cmd, ProtoBufBase* buf /* = nullptr */)
{
	bool result = false;
	bool retCode = false;

	MMOLOG_PROCESS_ERROR(m_pWebSession);

	if (buf == nullptr)
	{
		retCode = SendData(m_pWebSession, EMODULE_ID_SERV_SYS, cmd);
	}
	else
	{
		retCode = SendData(m_pWebSession, EMODULE_ID_SERV_SYS, cmd, buf);
	}
	MMOLOG_PROCESS_ERROR(retCode);

	result = true;
Exit0:
	return result;
}

bool MasterService::AddZoneSession(uint32_t zid, ISession* session)
{
    bool result = false;
    bool retCode = false;
	SESSION_USER_DATA userData;

	MMOLOG_PROCESS_ERROR(session);
	retCode = m_zoneSessionMap.insert(SESSION_MAP::value_type(zid, session)).second;
	MMOLOG_PROCESS_ERROR(retCode);

	userData.v32 = zid;
	session->SetMark(ZONE_SERVER);
	session->SetUserData(userData);

    result = true;
Exit0:
	return result;
}

bool MasterService::AddLoginSession(uint32_t loginId, ISession* session)
{
	bool result = false;
	bool retCode = false;
	SESSION_USER_DATA userData;

	retCode = m_loginSessionMap.insert(SESSION_MAP::value_type(loginId, session)).second;
	MMOLOG_PROCESS_ERROR(retCode);

	userData.v32 = loginId;
	session->SetMark(LOGIN_SERVER);
	session->SetUserData(userData);

	result = true;
Exit0:
	return result;
}

bool MasterService::AddWebSession(uint32_t webId, ISession* session)
{
    bool result = false;
    //bool retCode = false;

	SESSION_USER_DATA userData;
	MMOLOG_PROCESS_ERROR(session);

	m_pWebSession = session;

	userData.v32 = webId;
	session->SetMark(WEB_MASTER_SERVER);
	session->SetUserData(userData);

    result = true;
Exit0:
	return result;
}
